package indi.zn.commons.loader;


import static indi.zn.commons.loader.Loaders.*;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**  
*  
* @author 周楠
* @Description: 
*
* @date 2017年8月26日
*
*/
public class PackageScanner {
	
	private Logger logger = LoggerFactory.getLogger(PackageScanner.class);
	private String basePackage;
	private ClassLoader cl;
	    
	    
	    public PackageScanner() {
	        this.cl = getClass().getClassLoader();
	    }

	    /**
	     * Construct an instance and specify the base package it should scan.
	     * @param basePackage The base package to scan.
	     */
	    public PackageScanner(String basePackage) {
	        this.basePackage = basePackage;
	        this.cl = getClass().getClassLoader();

	    }

	    /**
	     * Construct an instance with base package and class loader.
	     * @param basePackage The base package to scan.
	     * @param cl Use this class load to locate the package.
	     */
	    public PackageScanner(String basePackage, ClassLoader cl) {
	        this.basePackage = basePackage;
	        this.cl = cl;
	    }

	    /**
	     * Get all fully qualified names located in the specified package
	     * and its sub-package.
	     *
	     * @return A list of fully qualified names.
	     * @throws IOException
	     */
	    public List<String> getFullyQualifiedClassNameList() throws IOException {
	        logger.info("开始扫描包{}下的所有类", basePackage);

	        return doScan(basePackage, new ArrayList<String>());
	    }
	    
	    
	    public List<String> doScan(String basePackage) throws IOException {
	    	
	    	 return doScan(basePackage, new ArrayList<String>());	
	    }
	    
	    public List<String> doScan(String[] basePackage) throws IOException {

	    	List<String> list=new ArrayList<String>();
	    	
	    	for (String string : basePackage) {
	    		doScan(string, list);	
			}
	    	
	    	 return list;
	    }

	    /**
	     * Actually perform the scanning procedure.
	     *
	     * @param basePackage
	     * @param nameList A list to contain the result.
	     * @return A list of fully qualified names.
	     *
	     * @throws IOException
	     */
	    public List<String> doScan(String basePackage, List<String> nameList) throws IOException {
	        // replace dots with splashes
	        String splashPath = dotToSplash(basePackage);

	        // get file path
	        URL url = cl.getResource(splashPath);
	        String filePath = getRootPath(url);

	        // Get classes in that package.
	        // If the web server unzips the jar file, then the classes will exist in the form of
	        // normal file in the directory.
	        // If the web server does not unzip the jar file, then classes will exist in jar file.
	        List<String> names = null; // contains the name of the class file. e.g., Apple.class will be stored as "Apple"
	        if (isJarFile(filePath)) {
	            // jar file
	            if (logger.isDebugEnabled()) {
	                logger.debug("{} 是一个JAR包", filePath);
	            }

	            names = readFromJarFile(filePath, splashPath);
	        } else {
	            // directory
	            if (logger.isDebugEnabled()) {
	                logger.debug("{} 是一个目录", filePath);
	            }

	            names = readFromDirectory(filePath);
	        }

	        for (String name : names) {
	            if (isClassFile(name)) {
	                //nameList.add(basePackage + "." + StringUtil.trimExtension(name));
	                nameList.add(toFullyQualifiedName(name, basePackage));
	            } else {
	                // this is a directory
	                // check this directory for more classes
	                // do recursive invocation
	                doScan(basePackage + "." + name, nameList);
	            }
	        }

	        if (logger.isDebugEnabled()) {
	            for (String n : nameList) {
	                logger.debug("找到{}", n);
	            }
	        }

	        return nameList;
	    }

	    /**
	     * Convert short class name to fully qualified name.
	     * e.g., String -> java.lang.String
	     */
	    private String toFullyQualifiedName(String shortName, String basePackage) {
	        StringBuilder sb = new StringBuilder(basePackage);
	        sb.append('.');
	        sb.append(trimExtension(shortName));

	        return sb.toString();
	    }

	    private List<String> readFromJarFile(String jarPath, String splashedPackageName) throws IOException {
	        if (logger.isDebugEnabled()) {
	            logger.debug("从JAR包中读取类: {}", jarPath);
	        }

	        JarInputStream jarIn = new JarInputStream(new FileInputStream(jarPath));
	        JarEntry entry = jarIn.getNextJarEntry();

	        List<String> nameList = new ArrayList<>();
	        while (null != entry) {
	            String name = entry.getName();
	            if (name.startsWith(splashedPackageName) && isClassFile(name)) {
	                nameList.add(name);
	            }

	            entry = jarIn.getNextJarEntry();
	        }

	        return nameList;
	    }

	    private List<String> readFromDirectory(String path) {
	        File file = new File(path);
	        String[] names = file.list();

	        if (null == names) {
	            return null;
	        }

	        return Arrays.asList(names);
	    }

	    private boolean isClassFile(String name) {
	        return name.endsWith(".class");
	    }

	    private boolean isJarFile(String name) {
	        return name.endsWith(".jar");
	    }

	    
	    private static String getRootPath(URL url) {
	        String fileUrl = url.getFile();
	        int pos = fileUrl.indexOf('!');

	        if (-1 == pos) {
	            return fileUrl;
	        }

	        return fileUrl.substring(5, pos);
	    }

	  
	    
	    public static void main(String[] args) throws Exception {
	    	PackageScanner scan = new PackageScanner("org.zn.commons.httpconnection");
	    	 List<String> list=scan.getFullyQualifiedClassNameList();
	    }
	    
}
